Server Actions
https://ja.react.dev/reference/rsc/server-actions#noun-labs-1201738-(2)
概要
Form からサーバの非同期関数を直接呼び出せる React の新しい機能
SvelteKit の Form actions とほとんど同じイメージ radish-miyazaki.icon
Next.js ではなく React 本体の機能
API Client を介して Route Handler を呼び出していた従来のアプローチと比べて、以下のようなメリットがある
中間コード(API Client)が無くなる
Browser 向けに バンドル されていた API Client が少なくなる
Hydration が完了する前に実行できる
従来は onSubmit イベントハンドラを利用していたため、Hydration が完了するまでユーザはフォーム送信ができなかった
Server Actions では <form> の action 属性を利用することで、Hydration が完了する前にフォーム送信が可能に
https://developer.mozilla.org/ja/docs/Web/HTML/Element/form#action
Progressive Enhancement を有効に
use server ディレクティブ
Server Action を利用するには、use server ディレクティブを宣言する必要がある
warning.icon use server ディレクティブは、サーバーサイドのファイルでのみ有効
https://arc.net/l/quote/nlhpellk
e.g. React Server Component
宣言場所は 2 通り
1. 非同期関数スコープの先頭
code:actions.ts
"use server"
export default function ServerComponent() {
async function myAction(formData: FormData) {
"use server";
// ...
}
}
2. Server Action 専用ファイルを作成する
名前は自由
code:actions.ts
"use server";
export async funtion myAction(formData: FormData) {
// ...
}
定義したファイルは Server Component と Client Component で import して用いる
code:tsx
"use client";
import { myAction } from './actions';
export default function ClientComponent({ id }: { id: string }) {
return (
<form action={myAction}>
<input type="hidden" name="id" value={id} />
<button type="submit">Add to Cart</button>
</form>
)
}
Server Action の関数は、第 1 引数に Web 標準の FormData オブジェクト を受け取る
このオブジェクトからフォームで入力された値を参照できる
code:ts
export async funtion myAction(formData: FormData) {
const id = formData.get("id");
if (typeof id !== "string") {
throw new Error("Validation error");
}
// ...
}
引数のバインド
https://arc.net/l/quote/lvtkhqbq
hidden フィールドを使用して <form> アクションにデータを渡す代わりに、bind メソッドを呼び出して追加の引数を渡すこともできます。
bind メソッドを用いることで、いくつかの引数がすでにバインドされた新しい Server Actiosn を作成可能
code:tsx
"use client";
import { myAction } from "./actions";
export default function ClientComponent({ id }: { id: string }) {
cosnt action = myAction.bind(null, id);
return (
<form action={myAction}>
<input type="hidden" name="id" value={id} />
<button type="submit">Add to Cart</button>
</form>
)
}
これにより、Server Action 関数はバインドされた引数の後ろに FormData を取るようになる
code:ts
export async funtion myAction(id: string, formData: FormData) {
// ...
}
Progressive Enhancement を維持したままバリデーションチェックを行う
Server Actions を用いて、Progressive Enhancement を維持したままバリデーションチェックを行うには、以下のような実装を行えば良い
Hydration が完了する前のフォーム送信: Client バリデーションを実行しない
Hydration が完了した後のフォーム送信: Client バリデーションを実行しない
これを実現するには、<form> の action と onSubmit を切り分ければ良い
実装例
code:tsx
export function PhotoForm({ photo, categories }: Props) {
const formState, formDispatch = useFormState(
updatePhoto,
initialFormState({ ...photo })
);
const clientErrors, setClientErrors = useState<FieldErrors | undefined>(
formState.error?.fieldErrors
);
const errors = clientErrors || formState.error?.fieldErrors;
function handleSubmit(event: FormEvent<HTMLFormElement>) {
try {
const formData = new FormData(event.currentTarget);
validateFormData(formData);
setClientErrors(undefined);
} catch (err) {
// Client バリデーションチェックに失敗した場合、preventDefault を呼び出して、action 実行を中断する
event.preventDefault();
if (!(err instanceof ZodError)) throw err;
setClientErrors(transformFiledErrors(err));
}
}
return (
<form action={formDispatch} onSubmit={handleSubmit}>
{/* ... */}
</form>
);
}
#React